<?php

class PHPMutagen_Runner
{

    protected $_baseDirectory = null;
    protected $_workingDirectory = null;
    protected $_sourceDirectory = null;
    protected $_specDirectory = null;
    protected $_adapterName = null;
    protected $_files = array();
    protected $_mutables = null;

    public function __construct() 
    {
    }

    public function execute() 
    {
        $this->prepare();
        $this->_generateMutations();

        /****************************************@@
         * Refactor Red Alert For All Code Below :)
         */

        $countMutants = 0;
        $countMutantsKilled = 0;
        $countMutantsEscaped = 0;
        $diffMutantsEscaped = array();

        // ensure all specs or tests are clean!
        $adapterClass = 'PHPMutagen_Adapter_'
            . ucfirst(strtolower($this->getAdapterName()));
        $adapter = new $adapterClass($this);
        $result = $adapter->execute();
        if (!$result) {
            $str = 'Before you face Mutants, you first need a passing Suite!' . PHP_EOL;
            $str .=  $adapter->getOutput();
            return $str;
        }
   
        // MUTANTS!!!
        foreach ($this->_mutables as $mutable) {
            $file = $mutable->getFilename();
            $originalFileContent = file_get_contents($file);
            $mutations = $mutable->getMutations();
            foreach ($mutations as $tokenIndex=>$mutation) {
                $mutatedFileContent = $mutation->mutate($originalFileContent, $tokenIndex);
                file_put_contents($file, $mutatedFileContent);
                
                $result = $adapter->execute();
                
                // result collation
                $countMutants++;
                if (!$result) { // careful - we want a FALSE result!
                    $countMutantsKilled++;
                } else { // tests all passing is a BAD thing :)
                    $countMutantsEscaped++;
                    $diffMutantsEscaped[] = $mutation->getDiff();
                }

                // revert to original state for next mutation
                file_put_contents($file, $originalFileContent);

                // small progress echo
                echo '.';
            }
        }

        $this->_deleteRecursive($this->getWorkingDirectory());
        
        // reporting
        $report = PHP_EOL;
        $report .= $countMutants;
        $report .= $countMutants == 1 ? ' Mutant' : ' Mutants';
        $report .= ' born out of the mutagenic slime!';
        $report .= PHP_EOL . PHP_EOL;
        $report .= $countMutantsKilled;
        $report .= $countMutantsKilled == 1 ? ' Mutant' : ' Mutants';
        $report .= ' exterminated!';
        $report .= PHP_EOL . PHP_EOL;
        if ($countMutantsEscaped > 0) {
            $report .= $countMutantsEscaped;
            $report .= $countMutantsEscaped == 1 ? ' Mutant' : ' Mutants';
            $report .= ' escaped; the integrity of your suite may be compromised by the following Mutants:';
            $report .= PHP_EOL . PHP_EOL;

            $i = 1;
            foreach ($diffMutantsEscaped as $mutantDiff) {
                $report .= $i . ') ' . PHP_EOL . $mutantDiff;
                $report .= PHP_EOL . PHP_EOL;
                $i++;
            }

            $report .= 'Happy Hunting! Remember that some Mutants may just be Ghosts (or if you want to be boring, false positives).';
        } else {
            $report .= 'No Mutants survived! Muahahahaha!';
        }

        return $report;
    }

    public function setBaseDirectory($path) 
    {
        $path = rtrim($path, ' \\/');
        $this->_baseDirectory = $path;
    }

    public function setWorkingDirectory($path) 
    {
        $path = rtrim($path, ' \\/');
        $this->_workingDirectory = $path;
    }

    public function setSourceDirectory($path) 
    {
        $path = rtrim($path, ' \\/');
        $this->_sourceDirectory = $path;
    }

    public function setSpecDirectory($path) 
    {
        $path = rtrim($path, ' \\/');
        $this->_specDirectory = $path;
    }

    public function setAdapterName($adapter) 
    {
        $this->_adapterName = $adapter;
    }

    public function getBaseDirectory() 
    {
        return $this->_baseDirectory;
    }

    public function getWorkingDirectory() 
    {
        return $this->_workingDirectory;
    }

    public function getSourceDirectory() 
    {
        return $this->_sourceDirectory;
    }

    public function getSpecDirectory() 
    {
        return $this->_specDirectory;
    }

    public function getAdapterName() 
    {
        return $this->_adapterName;
    }

    public function setGenerator(PHPMutagen_Generator $generator) 
    {
        $this->_generator = $generator;
    }

    public function getGenerator() 
    {
        if (!isset($this->_generator)) {
            $this->_generator = new PHPMutagen_Generator($this);
        }
        return $this->_generator;
    }

    public function getFiles() 
    {
        return $this->_files;
    }

    public function prepare() 
    {
        $this->_copyRecursive(
            $this->getBaseDirectory(),
            $this->getWorkingDirectory()
        );
        $this->_collateFiles($this->getSourceDirectory());
    }

    public function getMutables() 
    {
        return $this->_mutables;
    }

    protected function _collateFiles($target)
    {
        $d = dir($target);
        while (FALSE !== ($res = $d->read())) {
            if ($res == '.' || $res == '..') {
                continue;
            }
            $entry = $target . '/' . $res;           
            if (is_dir($entry)) {
                $this->_collateFiles($entry);
                continue;
            }
            $this->_files[] = $entry;
        }
        $d->close();
    }

    protected function _generateMutations() 
    {
        $generator = $this->getGenerator();
        $generator->generate();
        $this->_mutables = $generator->getMutables();
    }

    protected function _copyRecursive($source, $target)
    {
        @mkdir($target);
        $d = dir($source);
        while (FALSE !== ($res = $d->read())) {
            if ($res == '.' || $res == '..' || preg_match("/^\./", $res)) {
                continue;
            }
            $entry = $source . DIRECTORY_SEPARATOR . $res;           
            if (is_dir($entry)) {
                $this->_copyRecursive($entry, $target . DIRECTORY_SEPARATOR . $res);
                continue;
            }
            copy($entry, $target . DIRECTORY_SEPARATOR . $res);
        }
        $d->close();
    }
    
    protected function _deleteRecursive($target)
    {
        $d = dir($target);
        while (FALSE !== ($res = $d->read())) {
            if ($res == '.' || $res == '..') {
                continue;
            }
            $entry = $target . DIRECTORY_SEPARATOR . $res;           
            if (is_dir($entry)) {
                $this->_deleteRecursive($entry);
                continue;
            }
            unlink($entry);
        }
        // Just proving Windows is a fecked up platform!
        if (php_uname('s') !== 'Windows NT') {
            rmdir($target); // else delete manually on Windows XP
        }
        $d->close();
    }
}